<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <meta
      name="viewport"
      content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"
    />

    <title>新年到了，怎能少了烟花表演！祝大家新年快乐！！</title>
    <link rel="stylesheet" href="index.css" />

    <link rel="icon" type="image/x-icon" href="../../../favicon.ico" />
</head>

  <body>
    <div class="container">
      <div class="year">2025</div>
      <div class="greetings">新年快乐！</div>
    </div>
    <script>
      var vector = {
        _x: 1,
        _y: 0,

        create: function (x, y) {
          var obj = Object.create(this);
          obj.setX(x);
          obj.setY(y);
          return obj;
        },

        duplicate: function () {
          return vector.create(this._x, this._y);
        },

        random: function (maxX, maxY, minX, minY) {
          var x = Math.random() * (maxX - minX) + minX;
          var y = Math.random() * (maxY - minY) + minY;
          return vector.create(x, y);
        },

        setX: function (value) {
          this._x = value;
        },

        getX: function () {
          return this._x;
        },

        setY: function (value) {
          this._y = value;
        },

        getY: function () {
          return this._y;
        },

        bounds: function (size, mX, mY) {
          var mapsize = size;
          if (this._x > mX + mapsize) {
            this._x = -size;
          }
          if (this._x < -mapsize) {
            this._x = mX + size;
          }

          if (this._y > mY + mapsize) {
            this._y = -size;
          }
          if (this._y < -mapsize) {
            this._y = mY + size;
          }
        },

        outOfBounds: function (sx, sy, ex, ey) {
          if (this._x < sx) {
            return true;
          }
          if (this._x > ex) {
            return true;
          }

          if (this._y < sy) {
            return true;
          }
          if (this._y > ey) {
            return true;
          }
        },

        setAngle: function (angle) {
          var length = this.getLength();
          this._x = Math.cos(angle) * length;
          this._y = Math.sin(angle) * length;
        },

        getAngle: function () {
          return Math.atan2(this._y, this._x);
        },

        angleTo: function (p2) {
          return Math.atan2(p2.getY() - this._y, p2.getX() - this._x);
        },

        distanceTo: function (p2) {
          var dx = p2.getX() - this._x,
            dy = p2.getY() - this._y;

          return Math.sqrt(dx * dx + dy * dy);
        },

        setLength: function (length) {
          var angle = this.getAngle();
          this._x = Math.cos(angle) * length;
          this._y = Math.sin(angle) * length;
        },

        gravitateTo: function (p2, mass, mymass) {
          var grav = Vector.create(0, 0),
            dist = mypos.distanceTo(p2);

          //grav.setLength(mass / (dist * dist));
          grav.setLength((G * mass * mymass) / (dist * dist));
          grav.setAngle(this.angleTo(p2));
          this.addTo(grav);
        },

        getLength: function () {
          return Math.sqrt(this._x * this._x + this._y * this._y);
        },

        maxSpeed: function (s) {
          if (this.getLength() > s) {
            this.setLength(s);
          }
        },

        add: function (v2) {
          return vector.create(this._x + v2.getX(), this._y + v2.getY());
        },

        subtract: function (v2) {
          return vector.create(this._x - v2.getX(), this._y - v2.getY());
        },

        multiply: function (val) {
          return vector.create(this._x * val, this._y * val);
        },

        divide: function (val) {
          return vector.create(this._x / val, this._y / val);
        },

        addTo: function (v2) {
          this._x += v2.getX();
          this._y += v2.getY();
        },

        subtractFrom: function (v2) {
          this._x -= v2.getX();
          this._y -= v2.getY();
        },

        multiplyBy: function (val) {
          this._x *= val;
          this._y *= val;
        },

        divideBy: function (val) {
          this._x /= val;
          this._y /= val;
        },

        normalize: function (scale) {
          var norm = Math.sqrt(this._x * this._x + this._y * this._y);
          if (norm != 0) {
            return vector.create(
              (scale * this._x) / norm,
              (scale * this._y) / norm
            );
          }
        },

        dot: function (v2) {
          return this._x * v2._x + this._y * v2._y;
        },
      };
    </script>
    <script>
      const canvas = document.createElement("canvas"),
        context = canvas.getContext("2d"),
        width = (canvas.width = window.innerWidth),
        height = (canvas.height = window.innerHeight),
        HalfPI = Math.PI / 2,
        gravity = vector.create(0, 0.35);

      let objects = [];
      let startFireworks = true;

      document.body.appendChild(canvas);

      function random255() {
        return Math.floor(Math.random() * 100 + 155);
      }

      function randomColor() {
        let r = random255(),
          g = random255(),
          b = random255();
        return `rgb(${r}, ${g}, ${b})`;
      }

      class PhysicsBody {
        constructor() {
          objects.push(this);
        }
        PhysicsUpdate() {
          this.lastPosition = this.position.duplicate();
          this.position.addTo(this.velocity);
          this.velocity.addTo(gravity);
        }
        deleteObject() {
          objects[objects.indexOf(this)] = undefined;
        }
      }

      class firework extends PhysicsBody {
        constructor() {
          super();
          this.position = vector.create(Math.random() * width, height);

          let Velocity = vector.create(0, 0);
          Velocity.setLength(Math.random() * 10 + 15);
          Velocity.setAngle(Math.PI * 1.35 + Math.random() * Math.PI * 0.3);
          this.velocity = Velocity;

          this.trail = Math.floor(Math.random() * 4) != 1;
          this.trailColor = this.trail ? randomColor() : undefined;
          this.trailWidth = 2;

          this.TimeCreated = new Date().getTime();
          this.TimeExpired = this.TimeCreated + (Math.random() * 5 + 7) * 100;

          this.BlastParticleCount = Math.floor(Math.random() * 50) + 25;
          this.funky = Math.floor(Math.random() * 5) == 1;

          this.exposionColor = randomColor();
        }

        draw() {
          context.strokeStyle = this.trailColor;
          context.lineWidth = this.trailWidth;

          let p = this.position,
            lp = this.lastPosition;

          context.beginPath();
          context.moveTo(lp.getX(), lp.getY());
          context.lineTo(p.getX(), p.getY());
          context.stroke();
        }

        funkyfire() {
          var funky = this.funky;
          for (var i = 0; i < Math.floor(Math.random() * 10); i++) {
            new BlastParticle({ firework: this, funky });
          }
        }

        explode() {
          var funky = this.funky;
          for (var i = 0; i < this.BlastParticleCount; i++) {
            new BlastParticle({ firework: this, funky });
          }
          this.deleteObject();
        }

        checkExpire() {
          let now = new Date().getTime();
          if (now >= this.TimeExpired) this.explode();
        }

        render() {
          if (this.trail) this.draw();
          if (this.funky) this.funkyfire();
          this.checkExpire();
        }
      }

      class BlastParticle extends PhysicsBody {
        constructor({ firework, funky }) {
          super();
          this.position = firework.position.duplicate();

          let Velocity = vector.create(0, 0);
          if (!this.funky) {
            Velocity.setLength(Math.random() * 6 + 2);
            Velocity.setAngle(Math.random() * Math.PI * 2);
          } else {
            Velocity.setLength(Math.random() * 3 + 1);
            Velocity.setAngle(
              firework.getAngle + Math.PI / 2 - Math.PI * 0.25 + Math.PI * 0.5
            );
          }

          this.velocity = Velocity;

          this.color = firework.exposionColor;

          this.particleSize = Math.random() * 4;

          this.TimeCreated = new Date().getTime();
          this.TimeExpired = this.TimeCreated + (Math.random() * 4 + 3.5) * 100;
        }

        draw() {
          context.strokeStyle = this.color;
          context.lineWidth = this.particleSize;
          let p = this.position,
            lp = this.lastPosition;

          context.beginPath();
          context.moveTo(lp.getX(), lp.getY());
          context.lineTo(p.getX(), p.getY());
          context.stroke();
        }

        checkExpire() {
          let now = new Date().getTime();
          if (now >= this.TimeExpired) this.deleteObject();
        }

        render() {
          this.draw();
          this.checkExpire();
        }
      }

      document.body.addEventListener("mousedown", function (e) {
        let color = randomColor();
        for (var i = 0; i < Math.floor(Math.random() * 20) + 25; i++) {
          new BlastParticle({
            firework: {
              position: vector.create(e.pageX, e.pageY),
              velocity: vector.create(0, 0),
              exposionColor: color,
            },

            funky: false,
          });
        }
      });

      setInterval(function () {
        if (!startFireworks) return;
        for (var i = 0; i < Math.floor(Math.random() * 4); i++) {
          new firework();
        }
      }, 500);

      function cleanupObjects() {
        objects = objects.filter((o) => o != undefined);
      }

      function loop() {
        context.fillStyle = "rgba(0,0,0,0.085)";
        context.fillRect(0, 0, width, height);

        let unusedObjectCount = 0;
        objects.map((o) => {
          if (!o) {
            unusedObjectCount++;
            return;
          }
          o.PhysicsUpdate();
          o.render();
        });
        if (unusedObjectCount > 100) cleanupObjects();

        requestAnimationFrame(loop);
      }

      loop();
    </script>
  </body>
</html>
